package vq.codec.managers;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import vq.codec.entities.Dat;
import vq.codec.entities.Dictionary;
import vq.codec.exception.SystemException;
import vq.codec.util.FileUtil;

public class VQFileManagerImpl extends VQFileManager {

	private static VQFileManagerImpl instance = new VQFileManagerImpl();	
	
	public static VQFileManagerImpl getInstance() {
		return instance;
	}

	public Dat loadSourceFile(int quantizatorDimension, String filePath) {
		Dat dat = new Dat();
		
		BufferedReader reader = null;
		
		try {
			reader = new BufferedReader(new FileReader(filePath));
			reader.readLine();
			
			extractDimension(dat, reader);
			extractGrayScale(dat, reader);
			List<List<Integer>> pixelsMatrix =  extractPixelsMatrix(reader);
			
			int lineBlocks = dat.getHeight() / quantizatorDimension;
			for (int i = 1; i <= lineBlocks; i++) {
				for (int blockNumber = 0; (blockNumber < lineBlocks); blockNumber++) {
					readBlock(quantizatorDimension, dat, pixelsMatrix, blockNumber);
					deleteBlock(quantizatorDimension, pixelsMatrix, blockNumber);
				}
			}
		} catch (Throwable t) {
			throw new SystemException("Erro ao tentar carregar arquivo fonte.", t);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (Throwable t) {
					throw new SystemException("Erro ao tentar carregar arquivo fonte.", t);
				}
			}
		}
		
		return dat;
	}
	
	public Dictionary loadDictionaryFile(String filePath) {
		Dictionary dictionary = new Dictionary();
		
		BufferedReader reader = null;
		
		try {
			int quantizatorDimension = extractQuantizatorDimension(filePath);
			dictionary.setQuantizatorDimension(quantizatorDimension);
			
			reader = new BufferedReader(new FileReader(filePath));
			
			List<Double> vector = null;
			String vectorComponent = null;
			for (int i = 0; ((vectorComponent = reader.readLine()) != null); i++) {
				if (i % quantizatorDimension == 0) {
					vector = new ArrayList<Double>();
					dictionary.addVector(vector);
				}
				
				vector.add(Double.parseDouble(vectorComponent));
			}
		} catch (Throwable t) {
			throw new SystemException("Erro ao tentar carregar o arquivo de dicionário.", t);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (Throwable t) {
					throw new SystemException("Erro ao tentar carregar o arquivo de dicionário.", t);
				}
			}
		}
		
		return dictionary;
	}

	public List<Integer> loadEncodedFile(String filePath) {
		
		FileReader fileReader = null;
		BufferedReader buffReader = null;
		List<Integer> encoded = new ArrayList<Integer>();
		try {
			fileReader = new FileReader(filePath);
			buffReader = new  BufferedReader(fileReader);
			String[] encodedParts = buffReader.readLine().split(" ");
			
			for (int i = 0; i < encodedParts.length; i++) {
				
				encoded.add(Integer.parseInt(encodedParts[i]));
			}
		} catch (Throwable t) {
			throw new SystemException("Erro ao tentar ler arquivo codificado!", t);
		}finally{
			
			if(fileReader != null){
				try {
					fileReader.close();
				} catch (Throwable t) {
					throw new SystemException("Erro ao tentar ler arquivo codificado!", t);
				}
			}
		}
		
		return encoded;
	}
	
	public Dat loadDatFile(String filePath) {
		
		Dat dat = new Dat();
		
		BufferedReader reader = null;
		
		try {
			reader = new BufferedReader(new FileReader(filePath));
			
			String height = reader.readLine();
			dat.setHeight(Integer.parseInt(height));
			
			String width = reader.readLine();
			dat.setWidth(Integer.parseInt(width));
			
			String grayScale = reader.readLine();
			dat.setGrayScale(Integer.parseInt(grayScale));
			
			String vectorsString = reader.readLine();
			String[] stringVectors = vectorsString.split(" : ");
			for (String vectorString : stringVectors) {
				List<Integer> vector = new ArrayList<Integer>();
				for (String vectorElement : vectorString.split(" ")) {
					vectorElement = vectorElement.trim();
					vector.add(Integer.parseInt(vectorElement));
				}
				dat.getVectors().add(vector);
			}
		} catch (Throwable t) {
			throw new SystemException("Erro ao tentar ler o arquivo dat.", t);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (Throwable t) {
					throw new SystemException("Erro ao tentar ler o arquivo dat.", t);
				}
			}
		}
		
		return dat;
	}
	
	public void generateEncodedFile(List<Integer> encoded, String filePath) {
		
		PrintWriter writer =  null;
		try {
			writer = new PrintWriter(filePath + ".vq");
			
			for (int i = 0; i < encoded.size(); i++) {
				
				writer.print(encoded.get(i)+" ");
			}
		} catch (Throwable t) {
			throw new SystemException("Erro ao tentar gerar arquivo codificado!", t);
		}finally{
			
			if(writer != null){
				
				writer.close();
			}
		}
	}
	
	public void generateDatFile(Dat dat, String filePath) {
		PrintWriter writer = null;
		try {
			writer = new PrintWriter(filePath + ".dat");
			writer.println(dat.getHeight());
			writer.println(dat.getWidth());
			writer.println(dat.getGrayScale());
			for (List<Integer> vector : dat.getVectors()) {
				for (Integer element : vector) {
					writer.print(element + " ");
				}
				writer.print(": ");
			}
		} catch (Throwable t) {
			throw new SystemException("Erro ao tentar criar arquivo dat.", t);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (Throwable t) {
					throw new SystemException("Erro ao tentar carregar arquivo fonte.", t);
				}
			}
		}
	}

	public void generatePgmFile(Dat dat, String filePath) {
		int[][] result = new int[dat.getHeight()][dat.getWidth()];
		int lineBlock = 0;
		int count = 0;
		int collumBlock = 0;
		for (int i = 0; i < (dat.getHeight()*dat.getQuantizatorDimension()); i++) {
			for (int j = 0; j < dat.getQuantizatorDimension(); j++) {
				if(count == dat.getQuantizatorDimension()){
					count = 0;
					if(lineBlock == (dat.getHeight() - 1)){
						lineBlock = 0;
					}else{
						lineBlock++;
						collumBlock -= dat.getQuantizatorDimension();
					}
				}
				result[lineBlock][collumBlock] = dat.getVectors().get(0).get(j);
				collumBlock++;
				count++;
			}
			dat.getVectors().remove(0);
		}
		FileWriter writer = null;
		try {
			writer = new FileWriter(new File(filePath+".pgm"));
			BufferedWriter bufferedWriter = new BufferedWriter(writer);
			bufferedWriter.write("P2");
			bufferedWriter.newLine();
			bufferedWriter.write(" "+dat.getHeight()+" ");
			bufferedWriter.write(dat.getWidth()+" ");
			bufferedWriter.newLine();
			bufferedWriter.write(" "+dat.getGrayScale());
			bufferedWriter.newLine();
			for (int i = 0; i < result.length; i++) {
				for (int j = 0; j < result[i].length; j++) {
					bufferedWriter.write(String.valueOf(result[i][j])+" ");
				}
				bufferedWriter.newLine();
			}
		} catch (Throwable t) {
			throw new SystemException("Erro ao tentar criar o arquivo .pgm", t);
		} finally{
			if (writer != null) {
				try {
					writer.close();
				} catch (Throwable t) {
					throw new SystemException("Erro ao tentar carregar arquivo fonte.", t);
				}
			}
		}
	}
	
	private int extractQuantizatorDimension(String filePath) {
		int quantizatorDimension;
		String fileName = FileUtil.extractFileName(filePath);
		String[] fileNameParts = fileName.split("_");
		
		quantizatorDimension = Integer.parseInt(fileNameParts[1]);
		
		return quantizatorDimension;
	}
	
	private void extractDimension(Dat toReturn, BufferedReader fileReader) throws IOException {
		String dimension = fileReader.readLine().trim();
		String[] dimensionComponents = dimension.split(" ");

		int height = Integer.parseInt(dimensionComponents[0]);
		int width = Integer.parseInt(dimensionComponents[1]);

		toReturn.setHeight(height);
		toReturn.setWidth(width);
	}
	
	private List<List<Integer>> extractPixelsMatrix(BufferedReader fileReader) throws IOException {
		List<List<Integer>> pixelsMatrix = new ArrayList<List<Integer>>();
		String currentLine;
		while ((currentLine = fileReader.readLine()) != null) {
			String[] pixels = currentLine.split(" ");
			
			List<Integer> line = new ArrayList<Integer>();
			for (String pixel : pixels) {
				int pixelInt = Integer.parseInt(pixel);
				line.add(pixelInt);
			}
			
			pixelsMatrix.add(line);
		}
		
		return pixelsMatrix;
	}
	
	private void extractGrayScale(Dat toReturn, BufferedReader fileReader)
			throws IOException {
		String grayScaleString = fileReader.readLine();
		int grayScale = Integer.parseInt(grayScaleString.trim());
		toReturn.setGrayScale(grayScale);
	}
	
	private void deleteBlock(int quantizatorDimension,
			List<List<Integer>> pixelsMatrix, int blockNumber) {
		
		for (int lineOfBlock = (blockNumber * quantizatorDimension); lineOfBlock < ((blockNumber + 1) * quantizatorDimension); lineOfBlock++) {
			for (int columnOfBlock = 0; columnOfBlock < quantizatorDimension; columnOfBlock++) {
				pixelsMatrix.get(lineOfBlock).remove(0);
			}
		}
	}

	private void readBlock(int quantizatorDimension, Dat dat,
			List<List<Integer>> pixelsMatrix, int blockNumber) {
		
		for (int lineOfBlock = (blockNumber * quantizatorDimension); lineOfBlock < ((blockNumber + 1) * quantizatorDimension); lineOfBlock++) {
			List<Integer> block = new ArrayList<Integer>();
			for (int columnOfBlock = 0; columnOfBlock < quantizatorDimension; columnOfBlock++) {
				block.add(pixelsMatrix.get(lineOfBlock).get(columnOfBlock));
			}
			dat.getVectors().add(block);
		}
		
	}
	
}
